// ===================================
// <copyright>
// This file is part of LABSMFController.
//
//    LABSMFController is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    LABSMFController is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with LABSMFController.  If not, see <http://www.gnu.org/licenses/>.
//    Copyright 2011 David Bakker
//
//</copyright>
// <author>David Bakker</author>
// <email>mailto:DavidFBakker@gmail.com</email>
// <created>Saturday, May 07, 2011</created>
// <lastedit>Saturday, May 07, 2011</lastedit>
// ===================================

using System;
using System.Ext;
using System.Threading;
using LABSMFController.LABS.Classses;
using LABSMFController.LABS.Devices;

namespace LABSMFController.LABS.Services
{
    public class PumpServiceClass : BeerService
    {
        public static int MAXPumps = 2;

        public PumpDevice[] Pumps;
        private Thread[] _myThreads;
        private StepRunner[] _stepRunners;


        public void ResetAllPumps()
        {
            if (Pumps == null)
                SetupPumps();

            for (int c = 0; c < MAXPumps; c++)
            {
                if (Pumps != null) Pumps[c].TurnOff();
            }
        }

        public void SetupPumps()
        {
            Location.Setup();

            Pumps = new PumpDevice[MAXPumps];
            for (int c = 0; c < MAXPumps; c++)
            {
                switch (c)
                {
                    case 0:
                        Pumps[c] = new PumpDevice("Pump " + c,  Devs.DigitalDevice.Pump0.PIN);
                        break;
                    case 1:
                        Pumps[c] = new PumpDevice("Pump " + c, Devs.DigitalDevice.Pump1.PIN);
                        break;
                }
            }
        }

        public string GetPumpsClientOutPut()
        {
            string ret = "HEADER:Pumps:" + Pumps[0].Header + ";";
            for (int c = 0; c < MAXPumps; c++)
            {
                ret = ret + "\r\n" + Pumps[c].GetClientOutPut();
            }
            return ret;
        }

        public override void Configure()
        {
            Console.Write("Logging configure");


            _myThreads = new Thread[MAXPumps];
            _stepRunners = new StepRunner[MAXPumps];
        }

        public override void Start()
        {
            BeerProgram.Log("PumpService starting");
        }

        public override void Stop()
        {
            BeerProgram.Log("PumpService stoping");
            StopStep(0);
            StopStep(1);
        }

        public void StartStep(int waterMovementStep)
        {
            var waterMovement = LiquidMovementUtil.GetLiquidMovementClass(waterMovementStep);

          //  waterMovement.LiquidMovementDesitnationMethod = LiquidMovementDesitnationMethodENUM.Add;
           // waterMovement.SourceBubblerGallonsToMove = 1000;

            BeerProgram.Log("PumpService starting step " + waterMovement.Description);

            StopStep(waterMovement.PumpNumber);

            _stepRunners[waterMovement.PumpNumber] = new StepRunner(waterMovement);
            _myThreads[waterMovement.PumpNumber] = new Thread(_stepRunners[waterMovement.PumpNumber].Run);
            _myThreads[waterMovement.PumpNumber].Start();
        }

        public void StopStep(int pumpNumber)
        {
            if (pumpNumber < 0 || pumpNumber >= MAXPumps) return;
            BeerProgram.Log("PumpService stopping step " + pumpNumber);
            if (_stepRunners[pumpNumber] == null) return;
            _stepRunners[pumpNumber].Stop();
            _myThreads[pumpNumber].Abort();
        }
    }

    internal class StepRunner
    {
        private readonly LiquidMovementClass _liquidMovement;
        private bool _isRunning;
        private DateTime startTime;

        public StepRunner(LiquidMovementClass liquidMovement)
        {
            _liquidMovement = liquidMovement;
            StepNum = _liquidMovement.PumpNumber;
        }

        public bool Immediate { get; private set; }
        public int StepNum { get; private set; }

        private double SourceStartingGallons { get; set; }
        private double DestinationStartingGallons { get; set; }

        public bool IsRunning
        {
            get { return _isRunning; }
            private set
            {
                _isRunning = value;
                _liquidMovement.IsActive = value;
            }
        }

        public void Run()
        {
            if (_liquidMovement.IsActive) return;

            Immediate = true;

            _liquidMovement.IsActive = true;

            if (_liquidMovement.SourceBubblerID != -1)
            {
                SourceStartingGallons = BeerProgram.GetLevel(_liquidMovement.SourceBubblerID);
            }
            else
            {
                BeerProgram.Log("BeerStep " + StepNum + " source bubbler id is not set");
                Stop();
                return;
            }

            if (SourceStartingGallons <= 0)
            {
                BeerProgram.Log("BeerStep " + StepNum + " source is too low");
                IsRunning = false;
                Stop();
                return;
            }

            if (_liquidMovement.DestinationBubblerID != -1)
            {
                DestinationStartingGallons = BeerProgram.GetLevel(_liquidMovement.DestinationBubblerID);
            }

            BeerProgram.Log("BeerStep " + StepNum + " starting valves");
            for (int c = 0; c < _liquidMovement.ValveCount; c++)
            {
                _liquidMovement.Valves[c].TurnOn();
            }

            BeerProgram.Log("BeerStep " + StepNum + " starting vvalves");
            for (int c = 0; c < _liquidMovement.VValveCount; c++)
            {
                if (_liquidMovement.VValves[c].IsThreeWay)
                {
                    if (_liquidMovement.VValveLevel[c] == VValveDevices.VValves[c].ASwing)
                    {
                        _liquidMovement.VValves[c].TurnToA();
                    }
                    else
                    {
                        _liquidMovement.VValves[c].TurnToB();
                    }
                }
                else
                {
                    _liquidMovement.VValves[c].TurnLevel(_liquidMovement.VValveLevel[c], false);
                }
            }


            for (int c = 0; c < _liquidMovement.VValveCount; c++)
            {
                while (_liquidMovement.VValves[c].IsRunning)
                    Thread.Sleep(100);
            }

            BeerProgram.BubblerService.EmptyEvent += BubblerService_EmptyEvent;
            //  Stall start 
            if (!Immediate) Thread.Sleep(4000);

            BeerProgram.Log("BeerStep " + StepNum + " starting pumps");

            IsRunning = true;
            BeerProgram.PumpService.Pumps[_liquidMovement.PumpNumber].TurnOn();


            BeerProgram.Log("BeerStep " + StepNum + " is monitoring");
            if (_liquidMovement.DurationToRun != 0)
            {
                double d = _liquidMovement.DurationToRun*1000;
                Thread.Sleep(Convert.ToInt32(d.ToString()));
                Stop();
                return;
            }

            ErrorCount = 0;
           
            double lastGPM = 0;
            bool firstRun = true;
            
            

            while (CheckWaterLevels())
            {
                var gpm = BeerProgram.GetGPM10Seconds(_liquidMovement.SourceBubblerID);

                if (gpm == 0)
                    ++ErrorCount;

                if (firstRun)
                {
                    firstRun = false;
                    lastGPM = gpm;
                }
                else
                {
                    var test = gpm - lastGPM;
                    if ( test == 0 )
                    {
                        BeerProgram.Log("Beerstep " + StepNum + " seems to not be moving liquid");
                        ++ErrorCount;
                        Thread.Sleep(ToSleep);
                        return;
                    }

                    if ( test < 0 )
                    {
                        test = test*-1;
                    } 
                    
                    if (test < .32)
                    {
                        BeerProgram.Log("Beerstep " + StepNum + " running slow " + test);
                        ++ErrorCount;
                    }
                   
                }

                if (ErrorCount > MaxErrorCount)
                {
                    BeerProgram.Log("Beerstep " + StepNum + " ran too slow");
                    break;
                }
                else
                {
                    ++LastError;
                }
                Thread.Sleep(ToSleep);
            }

            Stop();
            return;
        }

        private const int ToSleep = 100;
        private const int MaxErrorCount = 20;
        
        private int LastError { get; set; }
        private int _errorCount;
        private int ErrorCount
        {
            get { return _errorCount; }
            set
            {
                //If no errors for over a minute ignore it for good behavior
                if (LastError > (60 * 1 * 1000) / ToSleep)
                {
                    LastError = 0;
                    return;
                }
                BeerProgram.Log("Beerstep " + StepNum + " errorcount increased to " + value + " and the max is " + MaxErrorCount);
                _errorCount = value;
            }
        }

        void BubblerService_EmptyEvent(object sender, TriggerMessage message)
        {
            var tt = message;
        }
        

        private bool CheckWaterLevels()
        {
            double sourcegl = BeerProgram.GetLevel(_liquidMovement.SourceBubblerID);
            if (sourcegl <= 0)
            {
                return false;
            }

            if (_liquidMovement.LiquidMovementSourceToMonitor.Equals(LiquidMovementToMonitorSourceENUM.Source) &&
                _liquidMovement.SourceBubblerGallonsToMove != 0)
            {
                if (SourceStartingGallons - _liquidMovement.SourceBubblerGallonsToMove <= sourcegl)
                    return false;
            }
            else if (_liquidMovement.LiquidMovementSourceToMonitor.Equals(LiquidMovementToMonitorSourceENUM.Destination) &&
                     _liquidMovement.DestinationBubblerGallons != 0 && _liquidMovement.DestinationBubblerID != -1)
            {
                double gl = BeerProgram.GetLevel(_liquidMovement.DestinationBubblerID);
                if (_liquidMovement.LiquidMovementDesitnationMethod.Equals(LiquidMovementDesitnationMethodENUM.Total))
                {
                    if (gl >= _liquidMovement.DestinationBubblerGallons)
                        return false;
                }

                if (DestinationStartingGallons + _liquidMovement.DestinationBubblerGallons >= gl)
                    return false;
            }

            return true;
        }

        public void Stop()
        {
            if (!Immediate) Thread.Sleep(1000);
            BeerProgram.Log("BeerStep " + StepNum + " stoping pumps");
            BeerProgram.PumpService.Pumps[_liquidMovement.PumpNumber].TurnOff();

            if (!Immediate) Thread.Sleep(2000);

            BeerProgram.Log("BeerStep " + StepNum + " stoping valves");
            for (int c = 0; c < _liquidMovement.ValveCount; c++)
            {
                _liquidMovement.Valves[c].TurnOff();
            }
            IsRunning = false;
            _liquidMovement.IsActive = false;
        }
    }
}